package com.github.mzule.fantasyslide;

import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.*;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.Canvas;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.multimodalinput.event.TouchEvent;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by CaoDongping on 9/6/16.
 */
public class FantasyDrawerLayout extends ComponentContainer implements Component.DrawTask,
        Component.TouchEventListener, Component.EstimateSizeListener,
        ComponentContainer.ArrangeListener {

    private SideBarWithBg leftSideBg;
    private SideBarWithBg rightSideBg;
    private SideBar leftSideBar;
    private SideBar rightSideBar;
    private SideBarWithBg currentSideBg;
    private Component contentLayout;
    private List<SideBar> sideBarChildren = new ArrayList<>();
    private float slideOffset;
    private float y;
    private boolean openLeftMenu = false;
    private Component imageComponent;
    private Button btnSwitch;
    private Context mContext;

    public boolean getOpenLeftMenu() {
        return openLeftMenu;
    }

    public void setOpenLeftMenu(boolean openMenu) {
        openLeftMenu = openMenu;
    }

    private int xx = 0;
    private int yy = 0;
    private int maxWidth = 0;
    private int maxHeight = 0;
    private int lastHeight = 0;

    // VelocityDetector，用来获取触发Touch事件的手指滑动速度
    protected VelocityDetector mVelocityDetector;
    //菜单滑动触发范围参数
    private int mTouchScale;
    //菜单是否正在被拖动
    private boolean mIsBeingDragged;
    //是否允许菜单被拖动
    private boolean mIsUnableToDrag;
    //最小滑动速度
    private int mMinimumVelocity;
    //最大滑动速度
    protected int mMaximumVelocity;
    //触发菜单迅速弹出展示或迅速弹回隐藏的手指在屏幕上移动的最小距离
    private int mFlingDistance;
    //触发Touch事件的触控点Id
    protected int mActivePointerId = INVALID_POINTER;
    //触控点无效标志
    private static final int INVALID_POINTER = -1;
    //记录上一次手指位置横坐标X
    private float mLastMotionX;
    //触发Touch事件的手指最初位置横坐标X
    private float mInitialMotionX;
    //current_state在菜单放置模式为左/右侧时使用，用于标记触发touch事件的当下菜单的放置位置：左侧或右侧
    private int current_state = SideGravity.LEFT_RIGHT;

    private final Map<Integer, Layout> axis = new HashMap<>();

    private static class Layout {
        int positionX = 0;
        int positionY = 0;
        int width = 0;
        int height = 0;
    }

    public FantasyDrawerLayout(Context context) {
        this(context, null);
    }

    public FantasyDrawerLayout(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public FantasyDrawerLayout(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        mContext = context;
        setEstimateSizeListener(this::onEstimateSize);
        setArrangeListener(this::onArrange);
        addDrawTask(this);
        setTouchEventListener(this::onTouchEvent);
        initCustomViewAbove();
    }

    //滑动菜单相关参数设置初始化
    private void initCustomViewAbove() {
        mTouchScale = 200;
        mMinimumVelocity = 1200;
        mMaximumVelocity = 22000;
        mFlingDistance = 80;
    }

    public void attachComponent(Component component, Button button) {
        imageComponent = component;
        if (button != null) {
            btnSwitch = button;
        }
    }

    protected void onFinishInflate() {
        classifyChildren();
//        wrapperSideBars();
    }


    @Override
    public void onDraw(Component component, Canvas canvas) {
        onFinishInflate();
    }

    /**
     * 对 child view 进行分裂,得到一个 contentLayout 和 两个 SideBar
     */
    private void classifyChildren() {
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            Component child = getComponentAt(i);
            if (child instanceof SideBar) {
                if (i == 1) {
                    rightSideBar = (SideBar) child;
                    rightSideBar.setDirection(SideGravity.RIGHT);
                    sideBarChildren.add(rightSideBar);
                } else {
                    leftSideBar = (SideBar) child;
                    leftSideBar.setDirection(SideGravity.LEFT);
                    sideBarChildren.add(leftSideBar);
                }
            } else {
                contentLayout = child;
            }
        }
    }

    /**
     * 包装  SideBar 成 SideBarWithBg,这样可以显示背景
     */
    private void wrapperSideBars() {
        for (SideBar sideBar : sideBarChildren) {
            removeComponent(sideBar);
            SideBarWithBg sideBarWithBg = SideBarWithBg.wrapper(sideBar);
            addComponent(sideBarWithBg);
            if (sideBar.getDirection() == SideGravity.LEFT) {
                leftSideBg = sideBarWithBg;
            } else if (sideBar.getDirection() == SideGravity.RIGHT) {
                rightSideBg = sideBarWithBg;
            }
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent ev) {
        final int action = ev.getAction();
        //初始化VelocityDetector
        if (mVelocityDetector == null) {
            mVelocityDetector= VelocityDetector.obtainInstance();
        }
        //将当前Touch事件与VelocityDetector绑定
        mVelocityDetector.addEvent(ev);
        //获得触发Touch事件的手指位置的横坐标X
        float position = ev.getPointerPosition(ev.getPointerId(ev.getIndex())).getX();
//        如果Touch事件在手机屏幕右侧触发范围内触发则设置current_state为RIGHT，反之设置为LEFT
        if (position >= getRight() - mTouchScale) {
            current_state = SideGravity.RIGHT;
        }
        if (position <= getLeft() + mTouchScale) {
            current_state = SideGravity.LEFT;
        }

        //Touch事件按类别进行处理
        switch (action) {
            //PRIMARY_POINT_DOWN即单指点击屏幕
            case TouchEvent.PRIMARY_POINT_DOWN:
                //获得当前touch事件index
                int index = ev.getIndex();
                //获得当前touch事件触控点Id
                mActivePointerId = ev.getPointerId(index);
                //记录下手指点击最初位置的横坐标和上一次手指点击位置的横坐标X
                mInitialMotionX = mLastMotionX  = ev.getPointerPosition(mActivePointerId).getX();
                break;
//            //POINT_MOVE即手指在屏幕上滑动
            case TouchEvent.POINT_MOVE:
                if (!mIsBeingDragged) {
                    //不允许拖动直接返回false
                    if (mIsUnableToDrag) {
                        return false;
                    }
                    //判断菜单是否被拖动
                    determineDrag(ev);

                }
                //菜单被拖动
                if (mIsBeingDragged) {
                    //获得当前touch事件index
                    int coordinate = 0;
                    float pageOffset = 0;
                    final int activePointerIndex = getPointerIndex(ev, mActivePointerId);
                    if (mActivePointerId == INVALID_POINTER) {
                        break;
                    }
                    //获得当前手指位置的横坐标X
                    final float x = ev.getPointerPosition(activePointerIndex).getX();
                    y = ev.getPointerPosition(activePointerIndex).getY();
                    if (x <= contentLayout.getLeft() + mTouchScale &&
                            current_state == SideGravity.LEFT) {
                        //获得当前主页面左侧边线横坐标，即此时左侧菜单已展开的宽度
                        coordinate  = contentLayout.getLeft();
                        //计算此时左侧菜单已展开的宽度占其总宽度的比例
                        pageOffset = (float) (coordinate) / getMenuWidth(leftSideBar);
                        mLastMotionX = x;
                        leftSideBar.setVisibility(VISIBLE);
                        currentSideBg = leftSideBg;
                        scrollLayout(x, leftSideBar, y, pageOffset, currentSideBg);
                    } else if (x >= contentLayout.getRight() - mTouchScale &&
                            current_state == SideGravity.RIGHT) {
                        //获得当前主页面右侧边线横坐标
                        coordinate  = contentLayout.getRight();
                        //计算此时左侧菜单已展开的宽度占其总宽度的比例,此时菜单已展开宽度等于屏幕总宽度减去主页面右侧边线横坐标
                        pageOffset = (float) (getScreenWidth() - coordinate) /
                                getMenuWidth(rightSideBar);
                        mLastMotionX = x;
                        currentSideBg = rightSideBg;
                        rightSideBar.setVisibility(VISIBLE);
                        scrollLayout(getScreenWidth() - x,
                                rightSideBar, y, pageOffset, currentSideBg);
                    }
                }
                break;
            /**
             * 手指抬起时根据此时的菜单展开状态决定手指抬起后菜单的状态
             */
            case TouchEvent.PRIMARY_POINT_UP:
                //如果此时菜单被拖动
                if (mIsBeingDragged) {
                    final VelocityDetector velocityTracker = mVelocityDetector;
                    velocityTracker.calculateCurrentVelocity(1000,
                            mMaximumVelocity,mMaximumVelocity);
                    int initialVelocity = (int) velocityTracker.getHorizontalVelocity();//获得当前的滑动速度
                    //菜单的放置模式为左侧
                    if (current_state == SideGravity.LEFT) {
                        contentLayout.setLeft(0);
                        leftSideBar.onMotionEventUp();
                        leftSideBar.setVisibility(INVISIBLE);
                        if (btnSwitch != null) {
                            btnSwitch.setBackground(new PixelMapElement(
                                    getPixelMapFromResource(mContext, ResourceTable.Media_menu)));
                        }
                    }
                    //菜单的放置模式为右侧
                    if (current_state == SideGravity.RIGHT) {
                        contentLayout.setRight(getScreenWidth());
                        rightSideBar.onMotionEventUp();
                        rightSideBar.setVisibility(INVISIBLE);
                    }

                    //获得当前touch事件Index
                    final int activePointerIndex = getPointerIndex(ev, mActivePointerId);
                    //如果事件有效
                    if (mActivePointerId != INVALID_POINTER) {
                        //获得当前手指位置的横坐标X
                        final float x = ev.getPointerPosition(activePointerIndex).getX();
                        //计算手指滑过的总距离
                        final int totalDelta = (int) (x - mInitialMotionX);
                    }
                    //处理完事件后，设置触控点Id为无效
                    mActivePointerId = INVALID_POINTER;
                    //结束菜单拖动
                    endDrag();
                } else {
                    if (getOpenLeftMenu() == true) {
                        AnimatorProperty animatorProperty = new AnimatorProperty();
                        animatorProperty.setTarget(imageComponent)
                                .moveFromX((leftSideBar.getWidth()))
                                .moveToX(0)
                                .setDuration(100)
                                .start();
                        if (btnSwitch != null) {
                            btnSwitch.setBackground(new PixelMapElement(
                                    getPixelMapFromResource(mContext,ResourceTable.Media_menu)));
                        }
                    }
                    setOpenLeftMenu(false);
                }
                break;
            //touch事件取消
            case TouchEvent.CANCEL:
//                //菜单被拖动
                if (mIsBeingDragged) {
                    endDrag();
                }
                break;
//            case TouchEvent.OTHER_POINT_DOWN: {
//                final int index_other = ev.getIndex();
//                mLastMotionX =  ev.getPointerPosition(index_other).getX();
//                mActivePointerId = ev.getPointerId(index_other);
//                break;
//            }
//            case TouchEvent.OTHER_POINT_UP:
//                int pointerIndex = getPointerIndex(ev, mActivePointerId);
//                if (mActivePointerId == INVALID_POINTER)
//                    break;
//                mLastMotionX = ev.getPointerPosition(pointerIndex).getX();
//                break;
        }
        return true;
    }

    private void scrollLayout(float x, SideBar sideBar, float y, float pageOffset, SideBarWithBg currentSideBg) {
        if (x > sideBar.getWidth()) {
            x = sideBar.getWidth();
        }
        if (sideBar.getDirection() == SideGravity.LEFT) {
            sideBar.setTouchY(y, pageOffset, true);
            contentLayout.setLeft((int) x);
            if (btnSwitch != null) {
                btnSwitch.setBackground(new PixelMapElement(
                        getPixelMapFromResource(mContext, ResourceTable.Media_return)));
            }
        } else {
            sideBar.setTouchY(y, pageOffset, false);
            contentLayout.setRight((int) (getScreenWidth() - x));
        }
    }

    private int getPointerIndex (TouchEvent ev, int id) {
        int activePointerIndex = ev.getIndex();
        if (activePointerIndex == -1) {
            mActivePointerId = INVALID_POINTER;
        }
        return activePointerIndex;
    }

    /**
     * 获得手机屏幕宽度
     *
     */
    public int getScreenWidth() {
        DisplayManager displayManager = DisplayManager.getInstance();
        Display display = displayManager.getDefaultDisplay(this.getContext()).get();
        DisplayAttributes displayAttributes = display.getAttributes();
        int ScreenR = displayAttributes.width;
        return ScreenR;
    }

    /**
     * 判断菜单是否被拖动
     * 如果当前touch事件index和触控点Id有效，且手指位置在触发菜单拖动的触发范围内，则菜单被拖动
     * 设置mIsBeingDragged为true;
     *
     */
    private void determineDrag(TouchEvent ev) {
        final int activePointerId = mActivePointerId;
        final int pointerIndex = getPointerIndex(ev, activePointerId);
        //获得手指位置的坐标X
        final float x = ev.getPointerPosition(activePointerId).getX();
        //PointerId和pointerIndex无效，菜单不能被拖动
        if (activePointerId == INVALID_POINTER || pointerIndex == INVALID_POINTER) {
            return;
        }
        if (x > getLeft() + mTouchScale && x < getRight() - mTouchScale) {//超出菜单拖动的触发区域，菜单不能被拖动
            return;
        }
        startDrag();
    }

    public int getMenuWidth(SideBar sideBar) {
        return sideBar.getWidth();
    }

    /**
     * 开始菜单拖动，设置mIsBeingDragged为true
     *
     */
    private void startDrag() {
        mIsBeingDragged = true;
    }

    //结束菜单拖动，对各个参数进行相应设置
    private void endDrag() {
        mIsBeingDragged = false;
        mIsUnableToDrag = false;
        //设置当前活跃触控点无效
        mActivePointerId = INVALID_POINTER;
        //清空当前VelocityDetector
        if (mVelocityDetector != null) {
            mVelocityDetector.clear();
            mVelocityDetector = null;
        }
    }

    /**
     * 通过资源ID获取位图对象
     *
     * @param context
     * @param resourceId
     * @return PixelMap
     */
    private PixelMap getPixelMapFromResource(Context context, int resourceId) {
        InputStream inputStream = null;
        try {
            // 创建图像数据源ImageSource对象
            inputStream = context.getResourceManager().getResource(resourceId);
            ImageSource.SourceOptions srcOpts = new ImageSource.SourceOptions();
            srcOpts.formatHint = "image/jpg";
            ImageSource imageSource = ImageSource.create(inputStream, srcOpts);
            // 设置图片参数
            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            return imageSource.createPixelmap(decodingOptions);
        } catch (IOException | NotExistException e) {
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                }
            }
        }
        return null;
    }

    @Override
    public boolean onEstimateSize(int widthEstimatedConfig, int heightEstimatedConfig) {
        invalidateValues();

        measureChildren(widthEstimatedConfig, heightEstimatedConfig);

        for (int idx = 0; idx < getChildCount(); idx++) {
            Component childView = getComponentAt(idx);
            switch (idx) {
                case 0:
                    addLeftChild(childView, idx, EstimateSpec.getSize(widthEstimatedConfig));
                    break;
                case 1:
                    addRightChild(childView, idx, EstimateSpec.getSize(widthEstimatedConfig));
                    break;
                case 2:
                    addBackground(childView);
                    break;
                default:
                    break;
            }
        }

        measureSelf(widthEstimatedConfig, heightEstimatedConfig);

        return true;
    }

    private void measureSelf(int widthEstimatedConfig, int heightEstimatedConfig) {
        int widthSpce = EstimateSpec.getMode(widthEstimatedConfig);
        int heightSpce = EstimateSpec.getMode(heightEstimatedConfig);
        int widthConfig = 0;
        switch (widthSpce) {
            case EstimateSpec.UNCONSTRAINT:
            case EstimateSpec.PRECISE:
                int width = EstimateSpec.getSize(widthEstimatedConfig);
                widthConfig = EstimateSpec.getSizeWithMode(width, EstimateSpec.PRECISE);
                break;
            case EstimateSpec.NOT_EXCEED:
                widthConfig = EstimateSpec.getSizeWithMode(maxWidth, EstimateSpec.PRECISE);
                break;
            default:
                break;
        }

        int heightConfig = 0;
        switch (heightSpce) {
            case EstimateSpec.UNCONSTRAINT:
            case EstimateSpec.PRECISE:
                int height = EstimateSpec.getSize(heightEstimatedConfig);
                heightConfig = EstimateSpec.getSizeWithMode(height, EstimateSpec.PRECISE);
                break;
            case EstimateSpec.NOT_EXCEED:
                heightConfig = EstimateSpec.getSizeWithMode(maxHeight, EstimateSpec.PRECISE);
                break;
            default:
                break;
        }
        setEstimatedSize(widthConfig, heightConfig);
    }

    private void addBackground(Component component) {
        Layout layout = new Layout();
        layout.positionX = component.getMarginLeft();
        layout.positionY = component.getMarginTop();
        layout.width = component.getEstimatedWidth();
        layout.height = component.getEstimatedHeight();
        axis.put(2, layout);
    }

    private void addLeftChild(Component component, int id, int layoutWidth) {
        Layout layout = new Layout();
        layout.positionX = component.getMarginLeft();
        layout.positionY = component.getMarginTop();
        layout.width = component.getEstimatedWidth();
        layout.height = component.getEstimatedHeight();
        if ((xx + layout.width) > layoutWidth) {
            xx = 0;
            yy += lastHeight;
            lastHeight = 0;
            layout.positionX = xx + component.getMarginLeft();
            layout.positionY = yy + component.getMarginTop();
        }
        axis.put(id, layout);
        lastHeight = Math.max(lastHeight, layout.height + component.getMarginBottom());
        xx += layout.width + component.getMarginRight();
        maxWidth = Math.max(maxWidth, layout.positionX + layout.width + component.getMarginRight());
        maxHeight = Math.max(maxHeight, layout.positionY +
                layout.height + component.getMarginBottom());
    }

    private void addRightChild(Component component, int id, int layoutWidth) {
        Layout layout = new Layout();
        layout.positionX = layoutWidth - component.getEstimatedWidth() - component.getMarginLeft();
        layout.positionY = component.getMarginTop();
        layout.width = component.getEstimatedWidth();
        layout.height = component.getEstimatedHeight();
        axis.put(id, layout);
    }

    private void measureChildren(int widthEstimatedConfig, int heightEstimatedConfig) {
        for (int idx = 0; idx < getChildCount(); idx++) {
            Component childView = getComponentAt(idx);
            if (childView != null) {
                LayoutConfig lc = childView.getLayoutConfig();
                int childWidthMeasureSpec;
                int childHeightMeasureSpec;

                if (lc.width == LayoutConfig.MATCH_CONTENT) {
                    childWidthMeasureSpec =
                            EstimateSpec.getSizeWithMode(lc.width, EstimateSpec.NOT_EXCEED);
                } else if (lc.width == LayoutConfig.MATCH_PARENT) {
                    int parentWidth = EstimateSpec.getSize(widthEstimatedConfig);;
                    int childWidth = parentWidth -
                            childView.getMarginLeft() - childView.getMarginRight();
                    childWidthMeasureSpec =
                            EstimateSpec.getSizeWithMode(childWidth, EstimateSpec.PRECISE);
                } else {
                    childWidthMeasureSpec =
                            EstimateSpec.getSizeWithMode(lc.width, EstimateSpec.PRECISE);
                }

                if (lc.height == LayoutConfig.MATCH_CONTENT) {
                    childHeightMeasureSpec =
                            EstimateSpec.getSizeWithMode(lc.height, EstimateSpec.NOT_EXCEED);
                } else if (lc.height == LayoutConfig.MATCH_PARENT) {
                    int parentHeight = EstimateSpec.getSize(heightEstimatedConfig);
                    int childHeight = parentHeight -
                            childView.getMarginTop() - childView.getMarginBottom();
                    childHeightMeasureSpec =
                            EstimateSpec.getSizeWithMode(childHeight, EstimateSpec.PRECISE);
                } else {
                    childHeightMeasureSpec =
                            EstimateSpec.getSizeWithMode(lc.height, EstimateSpec.PRECISE);
                }
                childView.estimateSize(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

    private void invalidateValues() {
        xx = 0;
        yy = 0;
        maxWidth = 0;
        maxHeight = 0;
        axis.clear();
    }

    @Override
    public boolean onArrange(int left, int top, int width, int height) {

        for (int idx = 0; idx < getChildCount(); idx++) {
            Component childView = getComponentAt(idx);
            Layout layout = axis.get(idx);
            if (layout != null) {
                childView.arrange(layout.positionX,
                        layout.positionY, layout.width, layout.height);
            }
        }
        return true;
    }


    //TODO 2021/8/30 
//    @Override
//    public boolean dispatchTouchEvent(MotionEvent ev) {
//        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//            if (leftSideBar != null && rightSideBar != null) {
//                if (isDrawerOpen(leftSideBar)) {
//                    currentSideBar = leftSideBar;
//                } else if (isDrawerOpen(rightSideBar)) {
//                    currentSideBar = rightSideBar;
//                } else if (ev.getX() < getWidth() / 2) {
//                    currentSideBar = leftSideBar;
//                } else {
//                    currentSideBar = rightSideBar;
//                }
//            } else {
//                currentSideBar = leftSideBar != null ? leftSideBar : rightSideBar;
//            }
//        }
//        if (ev.getAction() == MotionEvent.ACTION_UP) {
//            closeDrawers();
//            currentSideBar.onMotionEventUp();
//            return super.dispatchTouchEvent(ev);
//        }
//        y = ev.getY();
//        if (slideOffset < 1) {
//            super.dispatchTouchEvent(ev);
//        } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
//            currentSideBar.setTouchY(y, slideOffset);
//        }
//        return true;
//    }


//    @Override
//    public void onDrawerSlide(View drawerView, float slideOffset) {
//        this.slideOffset = slideOffset;
//        currentSideBar.setTouchY(y, slideOffset);
//        float translationX = drawerView.getWidth() * slideOffset / 2; // translation 为 sideBar 宽度的一半
//        if (GravityUtil.isLeft(GravityUtil.getGravity(drawerView))) {
//            contentLayout.setTranslationX(translationX);
//        } else {
//            contentLayout.setTranslationX(-translationX);
//        }
//        y = slideOffset == 0 ? 0 : y; // onDrawerClosed 并不是每次都会被触发,所以需要在这里判断。
//    }
}
